内存泄漏
不再用到的内存,没有及时释放,就叫做内存泄漏(memory leak)。
-
内存
从硬件层面看,计算机内存是由大量的 flip flops 所组成的(即大量的二进制电路所组成的)。每个 flip flop 包含少量晶体管并能够存储一个比特位。单个的 flip flops 可以通过一个唯一标识符寻址,所以就可以读和覆写它们。因此,理论上,我们可以把整个计算机内存看成是由一个巨大的比特位数组所组成的,这样就可以进行读和写。
-
静态内存分配
当编译代码时,编译器会检查原始数据类型并提前计算出程序运行所需要的内存大小。在所谓的静态堆栈空间中,所需的内存大小会被分配给程序。这些变量所分配到的内存所在的空间之所以被称为静态内存空间,是因为当调用函数的时候,函数所需的内存会被添加到现存内存的顶部。当函数中断,它们被以 LIFO(后进先出) 的顺序移出内存。
-
动态内存分配
但有时候,编译器并不知道函数需要多少内存,例如:数组。因此,就不能再堆栈中为程序分配内存空间。只有当程序运行时从操作系统中显式地分配到正确的内存空间,也就是动态分配内存空间。
-
区别
静态分配内存 动态分配内存 编译时所需内存大小固定已知 编译时所需内存大小未知不定 编译时优化 运行时优化 分配给栈 分配给堆 FILO先进后出,次序存放 无特别顺序,任意存放 线程独占 进程级别,线程共享 -
-
js自动释放内存(垃圾回收机制)
一般来说,编程语言喜欢多种内存管理方法。
var obj = { outerProp: { innerProp: 1 } }; // 创建两个对象,一个最外层对象,一个作为外层对象属性的内层对象。 // obj引用外层对象,不可被回收 var other = obj; // 外层对象两个引用 obj = 1; // 外层对象只有一个引用,即变量other var reference = other.innerProp; other = undefined; // 目前外层对象0引用,但因为外层对象的属性还有一个引用reference,因此还不能回收 reference = null; // 两个对象的引用都为0,现在可以回收了
- 引用计数(reference counting):语言引擎有一张”引用表”,保存了内存里面所有的资源(通常是各种值)的引用次数。如果一个值的引用次数是0,就表示这个值不再用到了,可以将这块内存释放。
let arr = [1, 2, 3, 4]; // arr引用了数组值,引用次数为1 console.log('hello world'); arr = null; // arr解除了引用,引用次数为0,内存自动释放
-
标记-清除算法
- 根:一般指代码中的引用的全局变量,如js中的window,nodejs中global
任何根变量到达不了的变量或者对象等,就可以标记为内存垃圾。算法起初会构建出一份根变量的完整列表,然后检测根变量及其后代变量是否处于激活状态,以此来标记是否是内存垃圾,整个标记阶段会中止 JavaScript 的运行。
为了控制垃圾回收的成本并且使得代码执行更加稳定,V8 使用增量标记法。也就是,不遍历整个内存堆,只是遍历一部分堆,然后重启正常的代码执行。下一个垃圾回收点将会从上一个堆遍历中止的地方开始执行,这是正常的代码执行过程中有一个非常短暂的间隙。
清除阶段由单独的线程来处理回收。 从2012年起,所有现代浏览器都使用了标记-清除垃圾回收算法。所有对JavaScript垃圾回收算法的改进都是基于标记-清除算法的改进,并没有改进标记-清除算法本身和它对“对象是否不再需要”的简化定义。
内存泄露的常见情况:
-
1、当页面中的元素被移除,若元素的绑定事件仍未被移除,部分浏览器可能存在内存泄露(IE);
解决办法:先手动移除绑定事件,然后移除元素,或者使用事件代理。
example:
<div id="myDiv"> <input type="button" value="Click me" id="myBtn"> </div> <script type="text/javascript"> document.onclick = function(event){ event = event || window.event; if(event.target.id == "myBtn"){ document.getElementById("myDiv").innerHTML = "Processing..."; } } </script>
-
2、对象之间的相互引用,部分浏览器可能存在不能释放,进而导致内存泄露;
解决办法:先解除相互引用关系,然后进行移除;
-
3、自动类型装箱转换(string -> String)
解决办法:先进行显式类型转换
example:
var s = '123'; console.log(s.length); //s本身是一个string并非object,并没有length属性,因此js引擎会临时创建一个String对象封装s, 而这个对象一定会泄露。 console.log((new String(s)).length); //显式转换
-
4、全局变量,未使用的全局变量,造成不必要全局污染和浪费
解决办法:使用严格模式,防止意外创建全局变量
内存优化手段
-
减少GC次数,减少内存占用
浏览器会不定时回收垃圾内存,称为 GC,不定时触发,一般在向浏览器申请新内存时,浏览器会检测是否到达一个临界值再进行触发。一般来说,GC 会较为耗时,GC 触发时可能会导致页面卡顿及丢帧。故我们要尽可能避免GC的触发。
-
使用对象池
初始化、实例化的成本高,且需要经常实例化,但每次实例化的数量较少的情况下,使用对象池可以获得显著的效能提升。
-
生产环境勿用 console.log 大对象,包括 DOM、大数组、ImageData、ArrayBuffer 等。因为 console.log 的对象不会被垃圾回收。
-
使用重复 DOM 等,如重复使用同一个弹窗而非创建多个。
-
ImageData, ArrayBuffer等合理使用,避免过多开销。